#include <kernel/manifold.h>
#include <kernel/manifold.h>
#include <kernel/component.h>
#include <kernel/link.h>

#include <mpi.h>

#include <string>
#include <iostream>
#include <cstdlib>

#include <stdint.h>

using namespace std;
using namespace manifold::kernel;

class message_t {
public:
  message_t(): message() {}
  message_t(string m):  message(m) {}

  void speak() const { cout << "Message says \"" << message << "\".\n"; }

  string message;
};

namespace manifold { namespace kernel {
  template<> size_t Get_serialize_size<message_t>(const message_t *m) {
    cout << "In get_serialize_size\n";
    return m->message.size() + sizeof(size_t);
  }

  template<> 
  size_t Serialize<message_t>(message_t *m, unsigned char *buf) 
  {
    // Remember to cast to a const pointer or you call the default!
    size_t s(Get_serialize_size((const message_t *)m));
    cout << "Serializing into buffer, size=" << s << ".\n";

    *((size_t*)buf) = m->message.size(); buf += sizeof(size_t);
    for (size_t i = 0; i < m->message.size(); i++, buf++) *buf = m->message[i];

    delete m;

    return s;
  }

  template<> message_t *Deserialize<message_t>(unsigned char *buf) {
    cout << "Deserializing!\n";
    message_t *m = new message_t();
    size_t bufsz = *((size_t*)buf); buf += sizeof(size_t);
    while (bufsz--) m->message += *(buf++);
    cout << "Deserialized message, size=" 
         << m->message.size() + sizeof(size_t) << '\n';
    return m;
  }
};};

class comp_a_t : public Component {
public:
  comp_a_t(string p): prefix(p) { 
    cout << "Created comp_a_t with prefix=\"" << p << "\"\n";
  }

  void do_event(int i, message_t *m) {
    cout << "comp_a_t(" << prefix << "): ";
    //m->speak();

    message_t *n = new message_t(m->message);
    delete m; 
    n->message += n->message;
    Send(0, n);
  }
private:
  string prefix;
};

class comp_b_t : public Component {
public:
  void do_event(int i, message_t *m) {
    cout << "comp_b_t:      ";
    //m->speak();

    message_t *n = new message_t(m->message);
    delete m;
    n->message += n->message;
    Send(0, n);
  }

  void bootstrap() {
    Send(0, new message_t("Initial"));
  }
};

int main(int argc, char** argv) {
  Clock masterclock(1);
  Manifold::Init(argc, argv);

  if (TheMessenger.get_node_size() != 2) {
    cerr << "Must pass \"-np 2\" to mpirun.\n";
    return 1;
  }   

  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);

  // These have to be called from both ranks. The constructors will only be 
  // called on the ranks on which these components actually live.
  CompId_t comp_a(Component::Create<comp_a_t>(1, "pfx")),
           comp_b(Component::Create<comp_b_t>(0));
  
  Manifold::Connect(comp_a, 0, comp_b, 1, &comp_b_t::do_event, 10);
  Manifold::Connect(comp_b, 0, comp_a, 1, &comp_a_t::do_event, 5);

  if (rank == 0) {
    Manifold::Schedule(0, &comp_b_t::bootstrap, 
                       Component::GetComponent<comp_b_t>(comp_b));
  }

  Manifold::StopAt(500);

  Component *compa_p = Component::GetComponent<comp_a_t>(0);
  Component *compb_p = Component::GetComponent<comp_b_t>(1);
  cout << rank << ": comp_a id=" << comp_a << ": " << compa_p << '\n' 
       << rank << ": comp_b id=" << comp_b << ": " << compb_p << '\n';

  Manifold::Run();

  MPI_Finalize();

  return 0;
}
